using static QRCoder.QRCodeGenerator;

namespace QRCoder;


/// <summary>
/// Represents a QR code generator that outputs QR codes as bitmap byte arrays.
/// </summary>
// ReSharper disable once InconsistentNaming
public class BitmapByteQRCode : AbstractQRCode, IDisposable
{
    private static readonly byte[] _bitmapHeaderPart1 = new byte[] { 0x42, 0x4D };
    private static readonly byte[] _bitmapHeaderPart2 = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00 };
    private static readonly byte[] _bitmapHeaderPartEnd = new byte[] { 0x01, 0x00, 0x18, 0x00 };

    /// <summary>
    /// Initializes a new instance of the <see cref="BitmapByteQRCode"/> class.
    /// Constructor without parameters to be used in COM objects connections.
    /// </summary>
    public BitmapByteQRCode() { }

    /// <summary>
    /// Initializes a new instance of the <see cref="BitmapByteQRCode"/> class with the specified <see cref="QRCodeData"/>.
    /// </summary>
    /// <param name="data"><see cref="QRCodeData"/> generated by the QRCodeGenerator.</param>
    public BitmapByteQRCode(QRCodeData data) : base(data) { }

    /// <summary>
    /// Returns the QR code graphic as a bitmap byte array.
    /// </summary>
    /// <param name="pixelsPerModule">The number of pixels each dark/light module of the QR code will occupy in the final QR code image.</param>
    /// <returns>Returns the QR code graphic as a bitmap byte array.</returns>
    public byte[] GetGraphic(int pixelsPerModule)
        => GetGraphic(pixelsPerModule, new byte[] { 0x00, 0x00, 0x00 }, new byte[] { 0xFF, 0xFF, 0xFF });

    /// <summary>
    /// Returns the QR code graphic as a bitmap byte array.
    /// </summary>
    /// <param name="pixelsPerModule">The number of pixels each dark/light module of the QR code will occupy in the final QR code image.</param>
    /// <param name="darkColorHtmlHex">The color of the dark modules in HTML hex format.</param>
    /// <param name="lightColorHtmlHex">The color of the light modules in HTML hex format.</param>
    /// <returns>Returns the QR code graphic as a bitmap byte array.</returns>
    public byte[] GetGraphic(int pixelsPerModule, string darkColorHtmlHex, string lightColorHtmlHex)
        => GetGraphic(pixelsPerModule, darkColorHtmlHex.HexColorToByteArray(), lightColorHtmlHex.HexColorToByteArray());

    /// <summary>
    /// Returns the QR code graphic as a bitmap byte array.
    /// </summary>
    /// <param name="pixelsPerModule">The number of pixels each dark/light module of the QR code will occupy in the final QR code image.</param>
    /// <param name="darkColorRgb">The color of the dark modules as an RGB byte array.</param>
    /// <param name="lightColorRgb">The color of the light modules as an RGB byte array.</param>
    /// <returns>Returns the QR code graphic as a bitmap byte array.</returns>
    public byte[] GetGraphic(int pixelsPerModule, byte[] darkColorRgb, byte[] lightColorRgb)
    {
        var sideLength = QrCodeData.ModuleMatrix.Count * pixelsPerModule;

        // Pre-calculate color/module bytes
        byte[] moduleDark = new byte[pixelsPerModule * 3];
        byte[] moduleLight = new byte[pixelsPerModule * 3];
        for (int i = 0; i < pixelsPerModule * 3; i += 3)
        {
            moduleDark[i] = darkColorRgb[2];
            moduleDark[i + 1] = darkColorRgb[1];
            moduleDark[i + 2] = darkColorRgb[0];
            moduleLight[i] = lightColorRgb[2];
            moduleLight[i + 1] = lightColorRgb[1];
            moduleLight[i + 2] = lightColorRgb[0];
        }

        // Pre-calculate padding bytes
        var paddingLen = sideLength % 4;

        // Calculate filesize (header + pixel data + padding)
        var fileSize = 54 + (3 * (sideLength * sideLength)) + (sideLength * paddingLen);

        // Bitmap container
        byte[] bmp = new byte[fileSize];
        int ix = 0;

        // Header part 1
        Array.Copy(_bitmapHeaderPart1, 0, bmp, ix, _bitmapHeaderPart1.Length);
        ix += _bitmapHeaderPart1.Length;

        // Filesize
        CopyIntAs4ByteToArray(fileSize, ix, bmp);
        ix += 4;

        // Header part 2
        Array.Copy(_bitmapHeaderPart2, 0, bmp, ix, _bitmapHeaderPart2.Length);
        ix += _bitmapHeaderPart2.Length;

        // Width
        CopyIntAs4ByteToArray(sideLength, ix, bmp);
        ix += 4;
        // Height
        CopyIntAs4ByteToArray(sideLength, ix, bmp);
        ix += 4;

        // Header end
        Array.Copy(_bitmapHeaderPartEnd, 0, bmp, ix, _bitmapHeaderPartEnd.Length);
        ix += _bitmapHeaderPartEnd.Length;

        // Add header null-bytes
        ix += 24;


        // Draw qr code
        for (var x = sideLength - 1; x >= 0; x -= pixelsPerModule)
        {
            var modMatrixX = (x + pixelsPerModule) / pixelsPerModule - 1;

            // Write data for first pixel of pixelsPerModule
            int posStartFirstPx = ix;
            for (var y = 0; y < sideLength; y += pixelsPerModule)
            {
                var module = QrCodeData.ModuleMatrix[modMatrixX][(y + pixelsPerModule) / pixelsPerModule - 1];
                Array.Copy(module ? moduleDark : moduleLight, 0, bmp, ix, moduleDark.Length);
                ix += moduleDark.Length;
            }
            // Add padding (to make line length a multiple of 4)
            ix += paddingLen;
            int lenFirstPx = ix - posStartFirstPx;

            // Re-write (copy) first pixel (pixelsPerModule - 1) times
            for (int pm = 0; pm < (pixelsPerModule - 1); pm++)
            {
                // Draw pixels
                Array.Copy(bmp, posStartFirstPx, bmp, ix, lenFirstPx);
                ix += lenFirstPx;
            }
        }

        return bmp;
    }


    /// <summary>
    /// Converts an integer to a 4 bytes and writes them to a byte array at given position
    /// </summary>
    /// <param name="inp">The integer to convert.</param>
    /// <param name="destinationIndex">Index of destinationArray where the converted bytes are written to</param>
    /// <param name="destinationArray">Destination byte array that receives the bytes</param>
    private static void CopyIntAs4ByteToArray(int inp, int destinationIndex, byte[] destinationArray)
    {
        unchecked
        {
            destinationArray[destinationIndex + 3] = (byte)(inp >> 24);
            destinationArray[destinationIndex + 2] = (byte)(inp >> 16);
            destinationArray[destinationIndex + 1] = (byte)(inp >> 8);
            destinationArray[destinationIndex + 0] = (byte)(inp);
        }
    }
}


/// <summary>
/// Provides helper functions for creating bitmap byte array QR codes.
/// </summary>
public static class BitmapByteQRCodeHelper
{
    /// <summary>
    /// Creates a bitmap byte array QR code with a single function call.
    /// </summary>
    /// <param name="plainText">The text or payload to be encoded inside the QR code.</param>
    /// <param name="pixelsPerModule">The number of pixels each dark/light module of the QR code will occupy in the final QR code image.</param>
    /// <param name="darkColorHtmlHex">The color of the dark modules in HTML hex format.</param>
    /// <param name="lightColorHtmlHex">The color of the light modules in HTML hex format.</param>
    /// <param name="eccLevel">The level of error correction data.</param>
    /// <param name="forceUtf8">Specifies whether the generator should be forced to work in UTF-8 mode.</param>
    /// <param name="utf8BOM">Specifies whether the byte-order-mark should be used.</param>
    /// <param name="eciMode">Specifies which ECI mode should be used.</param>
    /// <param name="requestedVersion">Sets the fixed QR code target version.</param>
    /// <returns>Returns the QR code graphic as a bitmap byte array.</returns>
    public static byte[] GetQRCode(string plainText, int pixelsPerModule, string darkColorHtmlHex,
        string lightColorHtmlHex, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false,
        EciMode eciMode = EciMode.Default, int requestedVersion = -1)
    {
        using var qrGenerator = new QRCodeGenerator();
        using var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode,
                requestedVersion);
        using var qrCode = new BitmapByteQRCode(qrCodeData);
        return qrCode.GetGraphic(pixelsPerModule, darkColorHtmlHex, lightColorHtmlHex);
    }

    /// <summary>
    /// Creates a bitmap byte array QR code with a single function call.
    /// </summary>
    /// <param name="txt">The text or payload to be encoded inside the QR code.</param>
    /// <param name="eccLevel">The level of error correction data.</param>
    /// <param name="size">The number of pixels each dark/light module of the QR code will occupy in the final QR code image.</param>
    /// <returns>Returns the QR code graphic as a bitmap byte array.</returns>
    public static byte[] GetQRCode(string txt, QRCodeGenerator.ECCLevel eccLevel, int size)
    {
        using var qrGen = new QRCodeGenerator();
        using var qrCode = qrGen.CreateQrCode(txt, eccLevel);
        using var qrBmp = new BitmapByteQRCode(qrCode);
        return qrBmp.GetGraphic(size);

    }
}
